module source.irc;

import    std.socket : Socket, AddressFamily, SocketType, InternetAddress, SocketOptionLevel, SocketOption;
import     core.time : dur;
import    std.string : strip;
import     std.array : split;
import      std.conv : to;
import std.algorithm : remove, canFind;

import source.logger : LoggerWriter;

class Irc
{
    private char[] host;
    private char[4096] buffer;
    private ushort port;
    private Socket sock;
    private LoggerWriter logger;
    private string[] channels;
    private string[] receiveBuffer;

    this(string server, int port, LoggerWriter writer)
    {
        this.host = server.dup;
        this.port = cast(ushort) port;
        this.logger = writer;
    }

    public void connect()
    {
        this.sock = new Socket(
            AddressFamily.INET,
            SocketType.STREAM);
        this.sock.connect(new InternetAddress(this.host, this.port));
    }

    public void register(string nickname, string username, string realname, string pass)
    {
        this.send("PASS "~pass);
        this.send("NICK "~nickname);
        this.send("USER "~username~" 8 localhost :"~realname);
    }

    public void destroy(string quitMessage = "Bye")
    {
        this.send("QUIT :"~quitMessage);
    }

    public string receive()
    {
        if (this.receiveBuffer.length == 0)
        {
            long rec = this.sock.receive(this.buffer);
            if (rec == -1)
            {
                this.receiveBuffer ~= "";
            }
            else
            {
                this.receiveBuffer ~= split(strip(to!string(this.buffer[0 .. rec])), "\r\n");
            }

            return this.receive();
        }
        else
        {
            string raw = this.receiveBuffer[0];
            string[] ex = split(raw, " ");
            ex.length = 30;

            this.receiveBuffer = this.receiveBuffer.remove(0);

            if (ex[0] == "PING")
            {
                this.logger.write("Ping");
                this.pong(ex[1]);
                return this.receive();
            }
            else
            {
                return raw;
            }
        }
    }

    public void privmsg(string dest, string dat)
    {
        this.send("PRIVMSG " ~ dest ~ " :" ~ dat);
    }

    public void send(string dat)
    {
        string[] toSend = split(dat, "\n");

        for (int i = 0; i < toSend.length; i++)
        {
            this.sock.send(toSend[i] ~ "\r\n");
            this.logger.write("< " ~ toSend[i]);
        }
    }

    public void joinChannel(string channel, string key = "")
    {
        if (this.channels.canFind(channel) == false)
        {
            string msg = "JOIN " ~ channel ~ " " ~ key;
            this.channels ~= channel;
            this.send(msg);
        }
    }

    public int partChannel(string channel)
    {
        if (this.channels.canFind(channel))
        {
            string msg = "PART " ~ channel;

            for (int i = 0; i < this.channels.length; i++)
            {
                if (this.channels[i] == channel)
                {
                    this.channels = this.channels.remove(i);
                    this.send(msg);
                    return 1;
                }
            }
        }
        return 0;
    }

    private void pong(string message)
    {
        this.sock.send("PONG "~message~"\r\n");
        this.logger.write("Pong");
    }

    public void setTimeout()
    {
        this.sock.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"msecs"(100));
        this.logger.write("Set non blocking");
    }
}
